
#ifndef __dragged_tab_controller_h__
#define __dragged_tab_controller_h__

#pragma once

#include <vector>

#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/timer.h"

#include "ui_gfx/rect.h"

#include "dock_info.h"
#include "tab_contents_wrapper.h"
#include "tab_contents_delegate.h"

namespace view
{
    class View;
}

class BaseTab;
class BaseTabStrip;
class DraggedTabView;
class TabStripModel;

struct TabRendererData;

///////////////////////////////////////////////////////////////////////////////
//
// DraggedTabController
//
//  An object that handles a drag session for an individual Tab within a
//  TabStrip. This object is created whenever the mouse is pressed down on a
//  Tab and destroyed when the mouse is released or the drag operation is
//  aborted. The Tab that the user dragged (the "source tab") owns this object
//  and must be the only one to destroy it (via |DestroyDragController|).
//
///////////////////////////////////////////////////////////////////////////////
class DraggedTabController : public TabContentsDelegate,
    public MessageLoopForUI::Observer
{
public:
    DraggedTabController();
    virtual ~DraggedTabController();

    // Initializes DraggedTabController to drag the tabs in |tabs| originating
    // from |source_tabstrip|. |source_tab| is the tab that initiated the drag and
    // is contained in |tabs|.  |mouse_offset| is the distance of the mouse
    // pointer from the origin of the first tab in |tabs| and |source_tab_offset|
    // the offset from |source_tab|. |source_tab_offset| is the horizontal distant
    // for a horizontal tab strip, and the vertical distance for a vertical tab
    // strip.
    void Init(BaseTabStrip* source_tabstrip,
        BaseTab* source_tab,
        const std::vector<BaseTab*>& tabs,
        const gfx::Point& mouse_offset,
        int source_tab_offset);

    // Returns true if there is a drag underway and the drag is attached to
    // |tab_strip|.
    // NOTE: this returns false if the dragged tab controller is in the process
    // of finishing the drag.
    static bool IsAttachedTo(BaseTabStrip* tab_strip);

    // Responds to drag events subsequent to StartDrag. If the mouse moves a
    // sufficient distance before the mouse is released, a drag session is
    // initiated.
    void Drag();

    // Complete the current drag session. If the drag session was canceled
    // because the user pressed Escape or something interrupted it, |canceled|
    // is true so the helper can revert the state to the world before the drag
    // begun.
    void EndDrag(bool canceled);

    // Returns true if a drag started.
    bool started_drag() const { return started_drag_; }

private:
    class DockDisplayer;
    friend class DockDisplayer;

    typedef std::set<HWND> DockWindows;

    // Enumeration of the ways a drag session can end.
    enum EndDragType
    {
        // Drag session exited normally: the user released the mouse.
        NORMAL,

        // The drag session was canceled (alt-tab during drag, escape ...)
        CANCELED,

        // The tab (NavigationController) was destroyed during the drag.
        TAB_DESTROYED
    };

    // Stores the date associated with a single tab that is being dragged.
    struct TabDragData
    {
        TabDragData();
        ~TabDragData();

        // The TabContentsWrapper being dragged.
        TabContentsWrapper* contents;

        // The original TabContentsDelegate of |contents|, before it was detached
        // from the browser window. We store this so that we can forward certain
        // delegate notifications back to it if we can't handle them locally.
        TabContentsDelegate* original_delegate;

        // This is the index of the tab in |source_tabstrip_| when the drag
        // began. This is used to restore the previous state if the drag is aborted.
        int source_model_index;

        // If attached this is the tab in |attached_tabstrip_|.
        BaseTab* attached_tab;
    };

    typedef std::vector<TabDragData> DragData;

    // Sets |drag_data| from |tab|. This also registers for necessary
    // notifications and resets the delegate of the TabContentsWrapper.
    void InitTabDragData(BaseTab* tab, TabDragData* drag_data);

    // Overridden from TabContentsDelegate:
    virtual TabContents* OpenURLFromTab(TabContents* source,
        const Url& url,
        const Url& referrer,
        WindowOpenDisposition disposition) OVERRIDE;
    virtual TabContents* OpenURLFromTab(TabContents* source,
        const OpenURLParams& params) OVERRIDE;
    virtual void NavigationStateChanged(const TabContents* source,
        unsigned changed_flags) OVERRIDE;
    virtual void AddNewContents(TabContents* source,
        TabContents* new_contents,
        WindowOpenDisposition disposition,
        const gfx::Rect& initial_pos,
        bool user_gesture) OVERRIDE;
    virtual void LoadingStateChanged(TabContents* source) OVERRIDE;
    virtual bool ShouldSuppressDialogs() OVERRIDE;

    // Overridden from MessageLoop::Observer:
    virtual void WillProcessMessage(const MSG& msg) OVERRIDE;
    virtual void DidProcessMessage(const MSG& msg) OVERRIDE;

    // Initialize the offset used to calculate the position to create windows
    // in |GetWindowCreatePoint|. This should only be invoked from |Init|.
    void InitWindowCreatePoint();

    // Returns the point where a detached window should be created given the
    // current mouse position.
    gfx::Point GetWindowCreatePoint() const;

    void UpdateDockInfo(const gfx::Point& screen_point);

    // Saves focus in the window that the drag initiated from. Focus will be
    // restored appropriately if the drag ends within this same window.
    void SaveFocus();

    // Restore focus to the View that had focus before the drag was started, if
    // the drag ends within the same Window as it began.
    void RestoreFocus();

    // Tests whether the position of the mouse is past a minimum elasticity
    // threshold required to start a drag.
    bool CanStartDrag() const;

    // Move the DraggedTabView according to the current mouse screen position,
    // potentially updating the source and other TabStrips.
    void ContinueDragging();

    // Handles dragging tabs while the tabs are attached.
    void MoveAttached(const gfx::Point& screen_point);

    // Handles dragging while the tabs are detached.
    void MoveDetached(const gfx::Point& screen_point);

    // Returns the compatible TabStrip that is under the specified point (screen
    // coordinates), or NULL if there is none.
    BaseTabStrip* GetTabStripForPoint(const gfx::Point& screen_point);

    DockInfo GetDockInfoAtPoint(const gfx::Point& screen_point);

    // Returns the specified |tabstrip| if it contains the specified point
    // (screen coordinates), NULL if it does not.
    BaseTabStrip* GetTabStripIfItContains(BaseTabStrip* tabstrip,
        const gfx::Point& screen_point) const;

    // Attach the dragged Tab to the specified TabStrip.
    void Attach(BaseTabStrip* attached_tabstrip, const gfx::Point& screen_point);

    // Detach the dragged Tab from the current TabStrip.
    void Detach();

    // Returns the index where the dragged TabContents should be inserted into
    // |attached_tabstrip_| given the DraggedTabView's bounds |dragged_bounds| in
    // coordinates relative to |attached_tabstrip_| and has had the mirroring
    // transformation applied.
    // NOTE: this is invoked from |Attach| before the tabs have been inserted.
    int GetInsertionIndexForDraggedBounds(const gfx::Rect& dragged_bounds) const;

    // Retrieve the bounds of the DraggedTabView relative to the attached
    // TabStrip. |tab_strip_point| is in the attached TabStrip's coordinate
    // system.
    gfx::Rect GetDraggedViewTabStripBounds(const gfx::Point& tab_strip_point);

    // Get the position of the dragged tab view relative to the attached tab
    // strip with the mirroring transform applied.
    gfx::Point GetAttachedDragPoint(const gfx::Point& screen_point);

    // Finds the Tabs within the specified TabStrip that corresponds to the
    // TabContents of the dragged tabs. Returns an empty vector if not attached.
    std::vector<BaseTab*> GetTabsMatchingDraggedContents(BaseTabStrip* tabstrip);

    // Does the work for EndDrag. If we actually started a drag and |how_end| is
    // not TAB_DESTROYED then one of EndDrag or RevertDrag is invoked.
    void EndDragImpl(EndDragType how_end);

    // Reverts a cancelled drag operation.
    void RevertDrag();

    // Reverts the tab at |drag_index| in |drag_data_|.
    void RevertDragAt(size_t drag_index);

    // Selects the dragged tabs in |model|. Does nothing if there are no longer
    // any dragged contents (as happens when a TabContents is deleted out from
    // under us).
    void ResetSelection(TabStripModel* model);

    // Finishes a succesful drag operation.
    void CompleteDrag();

    // Resets the delegates of the TabContents.
    void ResetDelegates();

    // Create the DraggedTabView.
    void CreateDraggedView(const std::vector<TabRendererData>& data,
        const std::vector<gfx::Rect>& renderer_bounds);

    // Utility for getting the mouse position in screen coordinates.
    gfx::Point GetCursorScreenPoint() const;

    // Returns the bounds (in screen coordinates) of the specified View.
    gfx::Rect GetViewScreenBounds(view::View* tabstrip) const;

    // Hides the frame for the window that contains the TabStrip the current
    // drag session was initiated from.
    void HideFrame();

    // Closes a hidden frame at the end of a drag session.
    void CleanUpHiddenFrame();

    void DockDisplayerDestroyed(DockDisplayer* controller);

    void BringWindowUnderMouseToFront();

    // Convenience for getting the TabDragData corresponding to the tab the user
    // started dragging.
    TabDragData* source_tab_drag_data()
    {
        return &(drag_data_[source_tab_index_]);
    }

    // Convenience for |source_tab_drag_data()->contents|.
    TabContentsWrapper* source_dragged_contents()
    {
        return source_tab_drag_data()->contents;
    }

    // Returns true if the tabs were originality one after the other in
    // |source_tabstrip_|.
    bool AreTabsConsecutive();

    // Returns the TabStripModel for the specified tabstrip.
    TabStripModel* GetModel(BaseTabStrip* tabstrip) const;

    // The TabStrip the drag originated from.
    BaseTabStrip* source_tabstrip_;

    // The TabStrip the dragged Tab is currently attached to, or NULL if the
    // dragged Tab is detached.
    BaseTabStrip* attached_tabstrip_;

    // The visual representation of the dragged Tab.
    scoped_ptr<DraggedTabView> view_;

    // The position of the mouse (in screen coordinates) at the start of the drag
    // operation. This is used to calculate minimum elasticity before a
    // DraggedTabView is constructed.
    gfx::Point start_screen_point_;

    // This is the offset of the mouse from the top left of the Tab where
    // dragging begun. This is used to ensure that the dragged view is always
    // positioned at the correct location during the drag, and to ensure that the
    // detached window is created at the right location.
    gfx::Point mouse_offset_;

    // Offset of the mouse relative to the source tab.
    int source_tab_offset_;

    // Ratio of the x-coordinate of the |source_tab_offset_| to the width of the
    // tab. Not used for vertical tabs.
    float offset_to_width_ratio_;

    // A hint to use when positioning new windows created by detaching Tabs. This
    // is the distance of the mouse from the top left of the dragged tab as if it
    // were the distance of the mouse from the top left of the first tab in the
    // attached TabStrip from the top left of the window.
    gfx::Point window_create_point_;

    // Location of the first tab in the source tabstrip in screen coordinates.
    // This is used to calculate window_create_point_.
    gfx::Point first_source_tab_point_;

    // The bounds of the browser window before the last Tab was detached. When
    // the last Tab is detached, rather than destroying the frame (which would
    // abort the drag session), the frame is moved off-screen. If the drag is
    // aborted (e.g. by the user pressing Esc, or capture being lost), the Tab is
    // attached to the hidden frame and the frame moved back to these bounds.
    gfx::Rect restore_bounds_;

    // The last view that had focus in the window containing |source_tab_|. This
    // is saved so that focus can be restored properly when a drag begins and
    // ends within this same window.
    view::View* old_focused_view_;

    // The position along the major axis of the mouse cursor in screen coordinates
    // at the time of the last re-order event.
    int last_move_screen_loc_;

    DockInfo dock_info_;

    DockWindows dock_windows_;

    std::vector<DockDisplayer*> dock_controllers_;

    // Timer used to bring the window under the cursor to front. If the user
    // stops moving the mouse for a brief time over a browser window, it is
    // brought to front.
    base::OneShotTimer<DraggedTabController> bring_to_front_timer_;

    // Did the mouse move enough that we started a drag?
    bool started_drag_;

    // Is the drag active?
    bool active_;

    DragData drag_data_;

    // Index of the source tab in drag_data_.
    size_t source_tab_index_;

    // True until |MoveAttached| is invoked once.
    bool initial_move_;

    DISALLOW_COPY_AND_ASSIGN(DraggedTabController);
};

#endif //__dragged_tab_controller_h__